package com.swak.fx.support.ui;

import java.util.List;

import org.controlsfx.glyphfont.FontAwesome;

import com.swak.utils.StringUtils;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.control.skin.TextFieldSkin;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.HitInfo;

/**
 * 文本框
 * 
 * @author 618lf
 */
public class UITextField extends TextField implements UILifecycle {

	// ================================================================================
	// Properties
	// ================================================================================
	private static final StyleablePropertyFactory<UITextField> FACTORY = new StyleablePropertyFactory<>(
			TextField.getClassCssMetaData());
	private final String STYLE_CLASS = "ui-text-field";
	private final String STYLESHEET = UI.load("css/UITextField.css");

	// ================================================================================
	// Constructors
	// ================================================================================
	public UITextField() {
		this(StringUtils.EMPTY);
	}

	public UITextField(String text) {
		this(text, StringUtils.EMPTY);
	}

	public UITextField(String text, String promptText) {
		super(text);
		setPromptText(promptText);
		init();
	}

	@Override
	public void initializeParts() {
		getStyleClass().add(STYLE_CLASS);
		setPrefColumnCount(6);
	}

	@Override
	public void layoutParts() {
	}

	@Override
	public void setupValueChangedListeners() {
		shadowProperty.addListener((ob, o, n) -> {
			if (n != null) {
				this.setEffect(n.shadowOf());
				this.setPickOnBounds(false);
			} else {
				this.setEffect(null);
				this.setPickOnBounds(true);
			}
		});
	}

	public UITextField ClearAble() {
		UIAttach.Clear().Attach(this);
		return this;
	}

	public UITextField registerEntryEventHandler(Runnable run) {
		this.addEventHandler(KeyEvent.KEY_TYPED, (e) -> {
			int code = e.getCharacter().hashCode();
			if (code == 13) {
				run.run();
			}
		});
		return this;
	}
	// ================================================================================
	// Property
	// ================================================================================

	private IntegerProperty maxWordProperty = new SimpleIntegerProperty();

	public IntegerProperty maxWordProperty() {
		return maxWordProperty;
	}

	public UITextField MaxWord(int words) {
		maxWordProperty.set(words);
		return this;
	}

	public int MaxWord() {
		return maxWordProperty.get();
	}

	// ================================================================================
	// Append
	// ================================================================================
	private ObjectProperty<Node> leftProperty = new SimpleObjectProperty<>(this, "left"); //$NON-NLS-1$
	private ObjectProperty<Node> rightProperty = new SimpleObjectProperty<>(this, "right"); //$NON-NLS-1$

	public final ObjectProperty<Node> leftProperty() {
		return leftProperty;
	}

	public final Node getLeft() {
		return leftProperty.get();
	}

	public final UITextField Left(Node value) {
		leftProperty.set(value);
		return this;
	}

	public final UITextField Left(FontAwesome.Glyph icon) {
		leftProperty.set(new UIIcon(icon));
		return this;
	}

	public final ObjectProperty<Node> rightProperty() {
		return rightProperty;
	}

	public final Node getRight() {
		return rightProperty.get();
	}

	public final UITextField Right(Node value) {
		rightProperty.set(value);
		return this;
	}

	public final UITextField Right(FontAwesome.Glyph icon) {
		rightProperty.set(new UIIcon(icon));
		return this;
	}

	// ================================================================================
	// Styleable Properties
	// ================================================================================
	private final StyleableObjectProperty<UI.Shadows> shadowProperty = new SimpleStyleableObjectProperty<>(
			StyleableProperties.SHADOW, this, "shadow", UI.Shadows.LEVEL0);

	public StyleableObjectProperty<UI.Shadows> shadowProperty() {
		return shadowProperty;
	}

	public void Shadow(UI.Shadows shadow) {
		this.shadowProperty.set(shadow);
	}

	public void Shadow() {
		this.shadowProperty.set(null);
	}

	// ================================================================================
	// CSSMetaData
	// ================================================================================

	private static class StyleableProperties {

		private static final List<CssMetaData<? extends Styleable, ?>> cssMetaDataList;

		private static final CssMetaData<UITextField, UI.Shadows> SHADOW = FACTORY
				.createEnumCssMetaData(UI.Shadows.class, "-ui-shadow", UITextField::shadowProperty, UI.Shadows.LEVEL0);

		static {
			cssMetaDataList = UILifecycle.cssMetaDataList(TextField.getClassCssMetaData(), SHADOW);
		}
	}

	public static List<CssMetaData<? extends Styleable, ?>> getControlCssMetaDataList() {
		return StyleableProperties.cssMetaDataList;
	}

	// ================================================================================
	// Overridden Methods
	// ================================================================================

	@Override
	protected Skin<?> createDefaultSkin() {
		return new UITextFieldSkin(this);
	}

	@Override
	public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
		return UITextField.getControlCssMetaDataList();
	}

	@Override
	public String getUserAgentStylesheet() {
		return STYLESHEET;
	}

	// ================================================================================
	// Skin
	// ================================================================================

	public static class UITextFieldSkin extends TextFieldSkin {

		private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes"); //$NON-NLS-1$
		private static final PseudoClass HAS_LEFT_NODE = PseudoClass.getPseudoClass("left-node-visible"); //$NON-NLS-1$
		private static final PseudoClass HAS_RIGHT_NODE = PseudoClass.getPseudoClass("right-node-visible"); //$NON-NLS-1$

		private Node left;
		private StackPane leftPane;
		private Node right;
		private StackPane rightPane;
		private final UITextField control;

		public UITextFieldSkin(UITextField control) {
			super(control);
			this.control = control;
			updateChildren();
			registerChangeListener(control.leftProperty(), e -> updateChildren());
			registerChangeListener(control.rightProperty(), e -> updateChildren());
			registerChangeListener(control.textProperty(), e -> updateLimit());
			registerChangeListener(control.maxWordProperty(), e -> updateLimit());
		}

		private void updateLimit() {
			int maxWord = control.MaxWord();
			String text = control.getText();
			int length = StringUtils.length(text);
			if (maxWord > 0 && maxWord < length) {
				control.textProperty().set(text.substring(0, maxWord));
			}
		}

		private void updateChildren() {
			Node newLeft = control.getLeft();
			getChildren().remove(leftPane);
			if (newLeft != null) {
				leftPane = new StackPane(newLeft);
				leftPane.setManaged(false);
				leftPane.setAlignment(Pos.CENTER_LEFT);
				leftPane.getStyleClass().add("left-pane"); //$NON-NLS-1$
				getChildren().add(leftPane);
				left = newLeft;
			} else {
				leftPane = null;
				left = null;
			}

			Node newRight = control.getRight();
			getChildren().remove(rightPane);
			if (newRight != null) {
				rightPane = new StackPane(newRight);
				rightPane.setManaged(false);
				rightPane.setAlignment(Pos.CENTER);
				rightPane.getStyleClass().add("right-pane"); //$NON-NLS-1$
				getChildren().add(rightPane);
				right = newRight;
			} else {
				rightPane = null;
				right = null;
			}

			control.pseudoClassStateChanged(HAS_LEFT_NODE, left != null);
			control.pseudoClassStateChanged(HAS_RIGHT_NODE, right != null);
			control.pseudoClassStateChanged(HAS_NO_SIDE_NODE, left == null && right == null);
		}

		@Override
		protected void layoutChildren(double x, double y, double w, double h) {
			final double fullHeight = h + snappedTopInset() + snappedBottomInset();
			final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.prefWidth(fullHeight));
			final double rightWidth = rightPane == null ? 0.0 : snapSizeX(rightPane.prefWidth(fullHeight));
			final double textFieldStartX = snapPositionX(x) + snapSizeX(leftWidth);
			final double textFieldWidth = w - snapSizeX(leftWidth) - snapSizeX(rightWidth);
			super.layoutChildren(textFieldStartX, 0, textFieldWidth, fullHeight);
			if (leftPane != null) {
				final double leftStartX = 0;
				leftPane.resizeRelocate(leftStartX, 0, leftWidth, fullHeight);
			}
			if (rightPane != null) {
				final double rightStartX = rightPane == null ? 0.0 : w - rightWidth + snappedLeftInset();
				rightPane.resizeRelocate(rightStartX, 0, rightWidth, fullHeight);
			}
		}

		@Override
		public HitInfo getIndex(double x, double y) {
			final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.prefWidth(getSkinnable().getHeight()));
			return super.getIndex(x - leftWidth, y);
		}

		@Override
		protected double computePrefHeight(double w, double topInset, double rightInset, double bottomInset,
				double leftInset) {
			final double ph = super.computePrefHeight(w, topInset, rightInset, bottomInset, leftInset);
			final double leftHeight = leftPane == null ? 0.0 : snapSizeY(leftPane.prefHeight(-1));
			final double rightHeight = rightPane == null ? 0.0 : snapSizeY(rightPane.prefHeight(-1));

			return Math.max(ph, Math.max(leftHeight, rightHeight));
		}

		@Override
		protected double computeMinWidth(double h, double topInset, double rightInset, double bottomInset,
				double leftInset) {
			final double mw = super.computeMinWidth(h, topInset, rightInset, bottomInset, leftInset);
			final double leftWidth = leftPane == null ? 0.0 : snapSizeX(leftPane.minWidth(h));
			final double rightWidth = rightPane == null ? 0.0 : snapSizeX(rightPane.minWidth(h));

			return mw + leftWidth + rightWidth;
		}

		@Override
		protected double computeMinHeight(double w, double topInset, double rightInset, double bottomInset,
				double leftInset) {
			final double mh = super.computeMinHeight(w, topInset, rightInset, bottomInset, leftInset);
			final double leftHeight = leftPane == null ? 0.0 : snapSizeY(leftPane.minHeight(-1));
			final double rightHeight = rightPane == null ? 0.0 : snapSizeY(rightPane.minHeight(-1));
			return Math.max(mh, Math.max(leftHeight, rightHeight));
		}
	}
}